package org.runtimedao.compiler;

import java.util.Arrays;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.log4j.Logger;
import org.runtimedao.exception.CompilerException;
import org.runtimedao.utils.CompilerUtils;
import org.runtimedao.utils.ConstantsUtils;

public class JavaCodeCompiler<T> {

	private static final String COMPILATION_FAILED = "Compilation failed";
	private static final String COMPILER_NOT_FOUND = "Compiler not found. If you are using eclipse, points it to a jdk";

	private static final Logger LOG = Logger.getLogger(JavaCodeCompiler.class);

	private JavaCompiler javaCompiler;
	private AppFileManager appFileManager;
	private AppClassLoader appClassLoader;
	private DiagnosticCollector<JavaFileObject> diagnosticListener;

	public JavaCodeCompiler() {
		LOG.info("Getting system java compiler");
		javaCompiler = ToolProvider.getSystemJavaCompiler();
		if (javaCompiler == null) {
			LOG.error(COMPILER_NOT_FOUND);
			throw new CompilerException(COMPILER_NOT_FOUND);
		}

		appClassLoader = new AppClassLoader(getClass().getClassLoader());
		diagnosticListener = new DiagnosticCollector<JavaFileObject>();
		StandardJavaFileManager standardFileManager = javaCompiler
				.getStandardFileManager(diagnosticListener, null, null);
		appFileManager = new AppFileManager(standardFileManager, appClassLoader);
	}

	public synchronized Class<T> compile(String packageName, String className,
			String javaSource) {

		String qualifiedClassName = CompilerUtils.getQualifiedClassName(
				packageName, className);
		LOG.info("Compiling " + qualifiedClassName);
		LOG.info(javaSource);

		AppJavaFileObject source = new AppJavaFileObject(className, javaSource);
		AppJavaFileObject compiled = new AppJavaFileObject(qualifiedClassName);
		appFileManager.setObjects(source, compiled);

		LOG.debug("Calling java compiler");
		CompilationTask task = javaCompiler.getTask(null, appFileManager,
				diagnosticListener, null, null, Arrays.asList(source));

		if (!task.call()) {
			String message = CompilerUtils
					.getCompilationError(diagnosticListener);
			LOG.error(COMPILATION_FAILED + ConstantsUtils.COLON_SPACE + message);
			throw new CompilerException(COMPILATION_FAILED);
		}

		@SuppressWarnings("unchecked")
		Class<T> newClass = (Class<T>) appClassLoader
				.findClass(qualifiedClassName);
		LOG.info(qualifiedClassName + " was compiled successfully");
		return newClass;
	}

}
